package replica;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Hashtable;

import org.omg.CORBA.ORB;

import administrator.administratorService;
import administrator.administratorServiceHelper;
import player.playerService;
import player.playerServiceHelper;
import common.CommonTools;
import common.ReplicaInfo;
import common.Marshall;
import common.UDPTools_1;

public class Replica {
	
	static int bufferSize = 1000;
	
	ReplicaServices replicaServices;
	
	private String role;
	UDPTools_1 uDPTools_1; 
		
	public Replica(String role){
		this.role = role;
		uDPTools_1 = new UDPTools_1();
		replicaServices = new ReplicaServices(role);
	}
	
	// this function may be used at the replica level to receive UDP message from  to the replica leader and reply
	// it can also be used at the leader to receive the message from the front end and reply
	
	public  void receiveReplyUDP(int UDPPort,String receiver) {		
		DatagramSocket serverSocket = null;
		InetAddress receiverAddress;
		byte[] bufferOut = new byte[bufferSize];
		byte[] bufferIn = new byte[bufferSize];
	
		boolean continueListening = true;
		
		try {
			serverSocket = new DatagramSocket(UDPPort);

			while (continueListening) {
				DatagramPacket request = new DatagramPacket(bufferIn, bufferIn.length);
				
				serverSocket.receive(request);
				String  requestMessage = new String(request.getData()).trim();

				CommonTools.writeLog(
						role,
						role,
						"New UDP Request received :" + requestMessage
								, "replica");
				
				//acknowledge
				String stringOut1 = "request received at:"+role +" request:"+Marshall.getValue("Method", requestMessage);
				bufferOut = stringOut1.getBytes();
				DatagramPacket acknowledge = new DatagramPacket(bufferOut,
						bufferOut.length, request.getAddress(),
						request.getPort());
				serverSocket.send(acknowledge);
				bufferOut = new byte[bufferSize];
				CommonTools.writeLog(
						role,
						role,
						"an acknowledgement was sent back for :" +Marshall.getValue("Method", requestMessage)
								, "replica");
				
				//process the request and reply				
				String stringOut = processRequest(role, requestMessage);
				bufferOut = stringOut.getBytes();
				DatagramPacket reply = new DatagramPacket(bufferOut,
						bufferOut.length, request.getAddress(),
						request.getPort());
				serverSocket.send(reply);
				
				CommonTools.writeLog(
								role,
								role,
								"A UDP response was sent back with the result:"
								+ stringOut, "replica");
				bufferOut = new byte[bufferSize];
				bufferIn = new byte[bufferSize];
			}

		} catch (SocketException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (serverSocket != null)
				serverSocket.close();
		}
	}
	
	private  String processRequest(String role, String  requestMessage) {
		
		String result = null;
		
		Hashtable results = new Hashtable();	
		results.clear();
		//steps
		// execute the request locally 
		// send the request to the replicas and get the result if i am the leader	
		//compare the results if i am the leader
		//send a notification to the manager
		//return  the result back
			
		// call the local execution.
		
		String result0 = executeLocalCall(requestMessage,role);
		//String result0 = "true";
		
		
		results.put(0, result0);
		
		// I have to manage time out using a thread	
		// this message should be  multicasted in the future
		String operationName	=	Marshall.getValue("Method", requestMessage);
		/*if(operationName.equals("playerStatus")){
			return result1;			
		}	*/	
		if(!role.equals("replica_0")){
			result = result0;
			
		} else {
			ArrayList<Integer> replicasList = getActiveReplicas();
			
			for(int i=0; i< replicasList.size();i++){  // starting from 1 as the 0 is the manager itself
				int id = replicasList.get(i);
				if(id != 0){
				CommonTools.writeLog(
						role,
						role,
						"A UDP request sent to replica_"+id+" :"
						+requestMessage, "replica");
				
				String resultTmp = uDPTools_1.sendReceiveUDP( Integer.parseInt(CommonTools.getproperty("replica.properties","UDPPort_" + "replica_"+id)),
																CommonTools.getproperty("replica.properties", "serverName_" + "replica_"+id), 
															   "replica_"+id,   "replica_0", requestMessage);
					results.put(id, resultTmp);
					CommonTools.writeLog(
							role,
							role,
							"We got A UDP response from replica_"+id+" :"
							+ resultTmp, "replica");
				}
			}
			result = GetBestResultNotifyRM(results);	
		}
		return result;
		
	}
	
	private String executeLocalCall(String UDPRequest,String server ){
				
		String serviceName		= 	Marshall.getValue("Service", UDPRequest);
		String operationName	=	Marshall.getValue("Method", UDPRequest);
		String geolocation  	= null;
		
		ORB orbEU = ORB.init(new String[1], null);
		ORB orbAS = ORB.init(new String[1], null);
		ORB orbNA = ORB.init(new String[1], null);
		
		//player
		if(serviceName.equals("player")){
			
			
			String playerIOR_EU = CommonTools.getIOR("player", server+"_EU","replica");
			String playerIOR_AS = CommonTools.getIOR("player", server+"_AS","replica");
			String playerIOR_NA = CommonTools.getIOR("player", server+"_NA","replica");

			org.omg.CORBA.Object playerServiceObjectEU = orbEU.string_to_object(playerIOR_EU);
			org.omg.CORBA.Object playerServiceObjectAS = orbAS.string_to_object(playerIOR_AS);
			org.omg.CORBA.Object playerServiceObjectNA = orbNA.string_to_object(playerIOR_NA);
			
			playerService playerServiceEU = playerServiceHelper.narrow(playerServiceObjectEU);
			playerService playerServiceAS = playerServiceHelper.narrow(playerServiceObjectAS);
			playerService playerServiceNA = playerServiceHelper.narrow(playerServiceObjectNA);
			
			PlayerClient playerClient	= null;
			String playerIPAddress	=	Marshall.getValue("PlayerIpAddress", UDPRequest);
			
			//get geolocation			
			
			if(CommonTools.isLegalIpAddress(playerIPAddress,"EU")){
				playerClient= new PlayerClient(playerServiceEU,role);
				geolocation = "EU";
			} else if(CommonTools.isLegalIpAddress(playerIPAddress,"AS")){
				playerClient= new PlayerClient(playerServiceAS,role);
				geolocation= "AS";
			} else {
				playerClient= new PlayerClient(playerServiceNA,role);
				geolocation ="NA";
			}
			
			
			
			//create			
			if(operationName.equals("createAccount")){
				
				return replicaServices.createPlayer(UDPRequest, geolocation, playerClient );
			}
			//sign in
			else if(operationName.equals("signIn")){
				return replicaServices.signInPlayer(UDPRequest, geolocation, playerClient );
				
			}
			//sign out
			else if(operationName.equals("signOut")){
				return replicaServices.signOutPlayer(UDPRequest, geolocation, playerClient );
			}
			//transfer
			else{
				return replicaServices.transferPlayer(UDPRequest, geolocation, playerClient );
			}
		}
		//admin
		else{
			
			String adminIOR_EU = CommonTools.getIOR("admin", server+"_EU","replica");
			String adminIOR_AS = CommonTools.getIOR("admin", server+"_AS","replica");
			String adminIOR_NA = CommonTools.getIOR("admin", server+"_NA","replica");
	
			org.omg.CORBA.Object administratorServiceEUObject = orbEU.string_to_object(adminIOR_EU);
			org.omg.CORBA.Object administratorServiceASObject = orbAS.string_to_object(adminIOR_AS);
			org.omg.CORBA.Object administratorServiceNAObject = orbNA.string_to_object(adminIOR_NA);
			
			administratorService administratorServiceEU = administratorServiceHelper.narrow(administratorServiceEUObject);
			administratorService administratorServiceAS = administratorServiceHelper.narrow(administratorServiceASObject);
			administratorService administratorServiceNA = administratorServiceHelper.narrow(administratorServiceNAObject);
			
			AdminClient adminClient = null;
			CommonTools.writeLog(
					role,
					role,
					"within admin:3" + UDPRequest
							, "replica"); 
			
			if(operationName.equals("halt")){
				geolocation ="EU3";
				adminClient= new AdminClient(administratorServiceEU, role);
			}else{
				String playerIPAddress	=	Marshall.getValue("AdminIpAddress", UDPRequest);
				if(CommonTools.isLegalIpAddress(playerIPAddress,"EU")){
					adminClient= new AdminClient(administratorServiceEU, role);
					geolocation = "EU";
				} else if(CommonTools.isLegalIpAddress(playerIPAddress,"AS")){
					adminClient= new AdminClient(administratorServiceAS,role);
					geolocation= "AS";
				} else {
					adminClient= new AdminClient(administratorServiceNA,role);
					geolocation ="NA";
				}
			}
			
			
			//player status
			if(operationName.equals("playerStatus")){
				return replicaServices.getPlayerStatus(UDPRequest, geolocation, adminClient );
			}
			//suspend account
			else if(operationName.equals("suspendAccount")){
				return replicaServices.suspendAccount(UDPRequest, geolocation, adminClient );
			}
			else{

				CommonTools.writeLog(
						role,
						role,
						"before halt call:" + UDPRequest
								, "replica");
				return replicaServices.halt(UDPRequest, geolocation, adminClient );
			}
			
		}
	}
	
	private  ArrayList<Integer> getActiveReplicas(){
		ArrayList<Integer> replicaslist = new ArrayList<Integer>();
		// steps:
		// send a UDP message to the manager to get the active replicas, 
		//unmarshal the message  and build the list
		
		//step1
		String requestMessage = null;
		requestMessage = Marshall.putTag(requestMessage, "Method", "getActiveReplicas");
		String resultTmp = uDPTools_1.sendReceiveUDP( Integer.parseInt(CommonTools.getproperty("replica.properties","UDPPort_" + "replica_manager")),
							CommonTools.getproperty("replica.properties", "serverName_" + "replica_manager"), 
							"replica_manager", role  , requestMessage);
		
		CommonTools.writeLog(
				role,
				role,
				"The replica:" + role +" got the list of active replicas:" + resultTmp
						, "replica");
		//step 2
		int i = 0;
		while(true){
			String idValue = Marshall.getValue("Id"+i, resultTmp);
			if(!idValue.equals("end")){
				replicaslist.add(Integer.parseInt(idValue));
				i++;
			}else{
				break;
			}
		}

		return replicaslist;
		
	}
	
	private  String GetBestResultNotifyRM(Hashtable<Integer, String> results){
		
		// this is a basic comparison we may un-marshall the string to make a sophisticated one
		// this based  on the assumption that only one result should be wrong
		String BestResult = null;
		
		Hashtable<String, Integer> majorityList = new Hashtable<String, Integer>();
		majorityList.clear();
		for(int id: results.keySet()){
			String value = results.get(id);
			if(majorityList.contains(value)){
				int actual =  majorityList.get(value);
				majorityList.put(value, actual + 1);
			} else{
				majorityList.put(value,1);				
			}
		}
		// get the one with the majority
		int majorityValue = 0;
		String majorityKey = null;
		for(String id: majorityList.keySet()){		
			int value =  majorityList.get(id);
			if (value >= majorityValue){
				majorityValue = value;
				majorityKey = id;				
			}
				
		}
		CommonTools.writeLog(
				role,
				role,
				"Our best result was: "+majorityKey, "replica");
		
		BestResult = majorityKey;
		
		String notifactionresult = sendNotification(results,BestResult);
		
		CommonTools.writeLog(
				role,
				role,
				"New notification send to the RM notifactionresult:" //+ notifactionresult
						, "replica");
		
		return BestResult;
	}	
	
	private String sendNotification(Hashtable<Integer, String> results,String bestResult) {
		String message = null;
		String result = null;
		
		message = Marshall.putTag(message, "Method", "updateCounters");
		int i =0;
		for(int id:results.keySet()){
			String value = results.get(id);
			if(value.equals(bestResult)){
				message = Marshall.putTag(message, "Result"+i, id+"=OK");
			}else{
				message = Marshall.putTag(message, "Result"+i, id+"=NOK");				
			}
			i++;
		}
		
		message = Marshall.putTag(message, "Result"+results.size(), "end");
		
		result = uDPTools_1.sendReceiveUDP(	Integer.parseInt(CommonTools.getproperty("replica.properties","UDPPort_" + "replica_manager")),
											CommonTools.getproperty("replica.properties", "serverName_" + "replica_manager"), 
										  	"replica_manager", 
										  	"replica_0", 
										  	message);
		return result;
	}
}
